use std::collections::HashSet;
use std::fmt;

use tiles::Connectors;

pub(crate) use self::slot_grid::SlotGrid;

mod slot_grid;
pub(crate) mod tiles;

#[cfg(test)]
mod tests {
    mod grid_slot;
    mod missing_tile_policy;
    mod tile_selection_policy;
    mod vacancy_policy;
}

/// Macro to implement Display using Debug (lowercase).
macro_rules! impl_display {
    ($t:ty) => {
        impl fmt::Display for $t {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "{}", format!("{:?}", self).to_lowercase())
            }
        }
    };
}

#[cfg_attr(test, derive(PartialEq))]
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum MissingTilePolicy {
    Avoid,
    Exit,
}

impl_display!(MissingTilePolicy);

#[cfg_attr(test, derive(PartialEq))]
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum TileSelectionPolicy {
    Priority,
    Random,
}

impl_display!(TileSelectionPolicy);

#[derive(clap::ValueEnum, Clone, Debug, PartialEq)]
pub enum VacancyPolicy {
    Avoid,
    AvoidStrict,
    Ignore,
}

impl fmt::Display for VacancyPolicy {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            VacancyPolicy::Avoid => write!(f, "avoid"),
            VacancyPolicy::AvoidStrict => write!(f, "avoid-strict"),
            VacancyPolicy::Ignore => write!(f, "ignore"),
        }
    }
}

#[cfg_attr(test, derive(Debug))]
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub(crate) enum Direction {
    North,
    East,
    South,
    West,
}

#[cfg_attr(test, derive(Clone, Debug, PartialEq))]
struct GridSlot {
    tile_id: Option<usize>,
    direction: Option<Direction>,
    predecessor: Option<(usize, usize)>,
    exposed_edges: Vec<Direction>,
    excluded_connectors: HashSet<Connectors>,
}

impl GridSlot {
    pub(crate) fn new(predecessor: Option<(usize, usize)>) -> Self {
        GridSlot {
            tile_id: None,
            direction: None,
            predecessor,
            exposed_edges: vec![],
            excluded_connectors: HashSet::new(),
        }
    }

    fn add_exposed_edge(&mut self, connection: Direction) {
        self.exposed_edges.push(connection);
    }

    fn next_exposed_edge(&self) -> Option<Direction> {
        self.exposed_edges.iter().next().copied()
    }

    fn remove_exposed_edge(&mut self, connection: Direction) {
        self.exposed_edges.retain(|e| *e != connection);
    }

    fn clear_exposed_edges(&mut self) {
        self.exposed_edges.clear();
    }

    fn add_excluded_connectors(&mut self, connectors: Connectors) {
        self.excluded_connectors.insert(connectors);
    }
}
